
import math

import sdxf
import radiance
import energyplus


class Point(object):
    def __init__(self, x=0.0, y=0.0, z=0.0):
        self.x = float(x)
        self.y = float(y)
        self.z = float(z)
 
    def __str__(self):
        return '(%0.4f, %0.4f, %0.4f)' % (self.x, self.y, self.z)
 
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y, self.z + other.z)
 
    def __sub__(self, other):
        return Point(self.x - other.x, self.y - other.y, self.z - other.z)
 
    def __mul__(self, f):
        return Point(self.x * f, self.y * f, self.z * f)
 
    def __repr__(self):
        return 'Point(%0.8f, %0.8f, %0.8f)' % (self.x, self.y, self.z)
 
    def dot_prod(self, other):
        return self.x*other.x + self.y*other.y + self.z*other.z
 
    def uv(self):
        return Point(self.x/self.magnitude(), self.y/self.magnitude(), self.z/self.magnitude())
 
    def cp(self, other):
        return Point(self.y*other.z - self.z*other.y, self.z*other.x - self.x*other.z, self.x*other.y - self.y*other.x)
 
    def magnitude(self):
        return (self.x**2 + self.y**2 + self.z**2)**0.5

class Building(object):
    
    def __init__(self):
        self.zones = []
        self.total_sensor_pts = []
        self.costfactor = []
        self.variables = None
        
    def add_variables(self,variables):
        self.variables = variables
        
    def dxf(self, path):
        
        d = sdxf.Drawing()
        for zone in self.zones:
            roof_mat = self.variables[11]
            for roof in zone.roofs:
                if roof_mat == 0:
                    d.append(sdxf.Face(points = roof.points, color = 7))
                if roof_mat == 1:
                    d.append(sdxf.Face(points = roof.points, color = 1))
            floor_mat = self.variables[12]
            for floor in zone.floors:
                if floor_mat == 0:
                    d.append(sdxf.Face(points = floor.points, color = 7))
                if floor_mat == 1:
                    d.append(sdxf.Face(points = floor.points, color = 1))
                    for flr_pt in floor.points:
                        floor_pt = Point(flr_pt[0], flr_pt[1], flr_pt[2]) - Point(0,0,1)
                        tup_floor_pt = (floor_pt.x,floor_pt.y,floor_pt.z) 
                        d.append(sdxf.Line(points = [flr_pt, tup_floor_pt], color = 3))
                
            counter = 0
            for wall in zone.walls:
                wall_mat = self.variables[counter + 3]
                counter +=1
                if wall_mat == 0 or wall_mat == 1:
                    d.append(sdxf.Face(points = wall.points, color = 7))
                    for window in wall.windows:
                        pt1 = Point(window.points[0][0], window.points[0][1], window.points[0][2])
                        pt2 = Point(window.points[1][0], window.points[1][1], window.points[1][2])
                        pt3 = Point(window.points[2][0], window.points[2][1], window.points[2][2])
                        v1 = pt2 - pt1
                        v2 = pt3 - pt2
                        x_product = v1.cp(v2)
                        magnitude = x_product.uv() * 0.1
                        moved_pts = []
                        for win_pt in window.points:
                            pt = Point(win_pt[0], win_pt[1], win_pt[2]) + magnitude
                            tup_pt = (pt.x,pt.y,pt.z)
                            moved_pts.append(tup_pt)
                            
                        if wall_mat == 0:
                            d.append(sdxf.Face(points = moved_pts, color = 4))
                            
                        if wall_mat == 1:
                            print "move points",len(moved_pts)
                            d.append(sdxf.Face(points = moved_pts, color = 5))
                            
                if wall_mat == 2:
                    print "wall points", len(wall.points)
                    d.append(sdxf.Face(points = wall.points, color = 7))
                    
                if wall_mat == 3:
                    d.append(sdxf.Face(points = wall.points, color = 1))
                    
                for shade in wall.shades:
                    d.append(sdxf.Face(points = shade.points, color = 7))
                    
        d.saveas(path)            
            
                    
    def sensor_pts(self,direction):
        sensor_pts_data = ""
        for sensor_pt in self.total_sensor_pts:
            sensor_pts_data = sensor_pts_data + radiance.write_sensor_file(sensor_pt, direction)
        return sensor_pts_data
        
    def rad(self,  interior = True):
        rad_building_data = ""
        for zone in self.zones:
            if interior == True :
                rad_building_data = rad_building_data + zone.rad(interior = True)
            if interior == False:
                rad_building_data = rad_building_data + zone.rad(interior = False)
    
        return rad_building_data
        
    def idf(self):
        #SEARCH FOR OVERLAPPING surfaces
        total_srfs= []
        same_srfs= []
        for zone in self.zones:
            for srf in zone.surfaces:
                total_srfs.append(srf)
        counter = 0
        for total_srf in total_srfs:
            for num_srf in range(len(total_srfs)):
                if num_srf <= len(total_srfs)-(2+counter):
                    total_srf_rev = total_srfs[num_srf+1+counter].points[:]
                    total_srf_rev.reverse()
                    if total_srf.points == total_srfs[num_srf +(1+counter)].points or total_srf.points == total_srf_rev:
                        total_srfs[num_srf +(1+counter)].boundary = "Surface" 
                        total_srfs[num_srf +(1+counter)].boundary_obj = total_srf.name 
                        total_srfs[num_srf +(1+counter)].sun_exp = "NoSun"
                        total_srfs[num_srf +(1+counter)].win_exp = "NoWind"
                        same_srfs.append(total_srfs[num_srf +(1+counter)])
                        if counter == len(total_srfs)-(2+counter):
                            total_srf.boundary = "Surface" 
                            total_srf.boundary_obj = total_srfs[num_srf +(1+counter)].name 
                            total_srf.sun_exp = "NoSun"
                            total_srf.win_exp = "NoWind"
                            same_srfs.append(total_srf)
            counter +=1
            
        zones_data = ""
        total_srfs_data = ""
        for zone in self.zones:
            zones_data = zones_data + zone.idf()
        for srfs in total_srfs:
            total_srfs_data = total_srfs_data + srfs.idf()
    
        building_data = zones_data + total_srfs_data
        return building_data
                        
class Zone(object):
    def __init__(self, name, building):
        self.name = name
        self.building = building
        self.surfaces = []
        self.walls = []
        self.floors = []
        self.roofs = []
        self.sensor_pts = None
        building.zones.append(self)
        
    def add_wall(self,  wall):
        wall.zone = self.name
        self.walls.append(wall)
        self.surfaces.append(wall)
        
        
    def add_floor(self,  floor):
        floor.zone = self.name
        self.floors.append(floor)
        self.surfaces.append(floor)
        
    def add_roof(self,  roof):
        roof.zone = self.name
        self.roofs.append(roof)
        self.surfaces.append(roof)
        
    def add_sensor_pts(self,  sensor_pt_list):
        self.sensor_pts = sensor_pt_list
        self.building.total_sensor_pts.append(sensor_pt_list)
        
    def sensor_pts(self,direction):
        if self.sensor_pts == None:
            raise Exception
        sensor_pts_data = radiance.write_sensor_file(self.sensor_pts, direction)
        return sensor_pts_data
        
    def idf(self):
        name = self.name 
        zone_data = energyplus.write_ep_zone(name)
        return zone_data
        
    def rad(self, interior = True):
        rad_zone_data = ""
        for surface in self.surfaces:
            if interior == True :
                rad_zone_data = rad_zone_data + surface.rad(interior = True)
            if interior == False:
                rad_zone_data = rad_zone_data + surface.rad(interior = False)
                
        return rad_zone_data

class Surface(object):
    def __init__(self, name, points):
        self.name = name
        self.points = points
        
    def material_idf(self,  material):
        self.idf_material = material
        
    def material_rad(self,  material):
        self.rad_material = material
        

class ZoneSurface(Surface):
    def __init__(self, name, points):
        super(ZoneSurface, self).__init__(name,  points)
        self.windows = []
        self.shades = []
        self.zone = None 
        self.boundary = "Outdoors" 
        self.boundary_obj = "" 
        self.sun_exp = "SunExposed" 
        self.win_exp = "WindExposed"
        
    def add_window(self,  window):
        self.windows.append(window)
        window.host_srf = self.name 
        
    def add_shade(self, shade):
        self.shades.append(shade)
        shade.host_srf = self.name 
        
class Wall(ZoneSurface):
    def __init__(self, name, points):
        super(Wall, self).__init__(name,  points)
        
    def idf(self):
        if self.zone == None or self.boundary == None or self.boundary_obj == None or self.sun_exp == None\
        or self.win_exp == None:
            raise Exception
            
        wall_name = self.name
        type = "Wall"
        construction = self.idf_material
        zone = self.zone
        boundary = self.boundary
        boundary_object = self.boundary_obj
        sun_exp = self.sun_exp
        wind_exp = self.win_exp
        points = self.points
        wall_data = energyplus.write_ep_surface(wall_name, type,construction, zone, 
                                                boundary,boundary_object, sun_exp, wind_exp, points)
        #window 
        if self.windows !=[]:
            for window in self.windows:
                wall_data = wall_data + window.idf()
        #shade 
        if self.shades != []:
            for shade in self.shades:
                wall_data = wall_data + shade.idf()
                
        return wall_data
        
    #TODO MAKE THIS FUNCTION MORE GENERAL AS NOW IT ONLY DEALS WITH ONE WINDOW PER SURFACE
    def rad(self, interior = True):
        wall_name = self.name 
        wall_material = self.rad_material
        
        #wall with no windows
        if self.windows == []:
            wall_points = self.points[:]
            if interior == True:
                wall_points.reverse()
                
            rad_wall_data = radiance.write_surface(wall_name, wall_material, wall_points)
                
        #wall with windows
        if self.windows !=[]:
            window = self.windows[0]
            wall_points = self.points[:]
            win_points = window.points[:]
            if interior == True:
                wall_points.reverse()
                window_points = window.rad(interior = True )
            if interior == False:
                win_points.reverse()
                window_points = window.rad(interior = False)
                
            wall_win_points = wall_points  
            wall_win_points.append(wall_points[0])
            #THE LAST POINT IS A REFERENCE POINT 
            ref_pt = Point(wall_points[0][0], wall_points[0][1], wall_points[0][2])
            #FIND OUT THE DISTANCE OF THE WINDOW POINTS TO THE REFERENCE POINTS AND TAKE THE NEAREST POINT
            mag = []
            for win_point in win_points:
                v = ref_pt - Point(win_point[0], win_point[1], win_point[2])
                v_mag = v.magnitude()
                mag.append(v_mag)
                
            sort_mag = sorted(mag)
            index = mag.index(sort_mag[0]) #THE NEAEREST POINT
            #REARRANGE THE POINTS ACCORDING TO THE NEAREST POINT 
            sort_win_points = win_points[index:] + win_points[:index]
            #ARRANGE THE WALL POINTS WITH THE WIN POINTS 
            wall_win_points = wall_win_points + sort_win_points 
            wall_win_points.append(sort_win_points[0])
            rad_wall_data = radiance.write_surface(wall_name, wall_material, wall_win_points) + window_points
        
        #shade 
        if self.shades !=[]:
            for shade in self.shades:
                rad_wall_data = rad_wall_data + shade.rad()
                
        return rad_wall_data
        
    
class Roof(ZoneSurface):
    def idf(self):
        if self.zone == None or self.boundary == None or self.boundary_obj == None or self.sun_exp == None\
        or self.win_exp == None:
            raise Exception
            
        roof_name = self.name
        type = "Roof"
        construction = self.idf_material
        zone = self.zone
        boundary = self.boundary
        boundary_object = self.boundary_obj
        sun_exp = self.sun_exp
        wind_exp = self.win_exp
        points = self.points
        roof_data = energyplus.write_ep_surface(roof_name, type,construction, zone, 
                                                boundary,boundary_object, sun_exp, wind_exp, points)
                                                
        #window 
        if self.windows !=[]:
            for window in self.windows:
                roof_data = roof_data + window.idf()
        #shade 
        if self.shades != []:
            for shade in self.shades:
                roof_data = roof_data + shade.idf()
                
        return roof_data
    
    #TODO MAKE THIS FUNCTION MORE GENERAL AS NOW IT ONLY DEALS WITH ONE WINDOW PER SURFACE
    def rad(self, interior = True):
        roof_name = self.name 
        roof_material = self.rad_material
        
        #wall with no windows
        if self.windows == []:
            roof_points = self.points[:]
            if interior == True:
                roof_points.reverse()
                
            rad_roof_data = radiance.write_surface(roof_name, roof_material, roof_points)
                
        #wall with windows
        if self.windows !=[]:
            window = self.windows[0]
            roof_points = self.points[:]
            win_points = window.points[:]
            if interior == True:
                roof_points.reverse()
                window_points = window.rad(interior = True )
            if interior == False:
                win_points.reverse()
                window_points = window.rad(interior = False )
                
            roof_win_points = roof_points  
            roof_win_points.append(roof_points[0])
            for win_point in win_points:
                roof_win_points.append(win_point)
            roof_win_points.append(win_points[0])
            
            rad_roof_data = radiance.write_surface(roof_name, roof_material, roof_win_points) + window_points
        
        #shade 
        if self.shades !=[]:
            for shade in self.shades:
                rad_roof_data = rad_roof_data + shade.rad()
                
        return rad_roof_data
        
class Floor(ZoneSurface):
    def __init__(self, name, points,boundary_cond):
        super(Floor, self).__init__(name,  points)
        self.boundary_cond = boundary_cond
        
    def idf(self):
        flr_name = self.name
        type = "Floor"
        construction = self.idf_material
        zone = self.zone
        boundary = self.boundary_cond
        boundary_object = self.boundary_obj
        points = self.points
        if self.boundary_cond == "Ground":
            sun_exp = "NoSun"
            wind_exp = "NoWind"
            
        if self.boundary_cond == "Outdoors":
            sun_exp = "SunExposed"
            wind_exp = "WindExposed"
            
        flr_data = energyplus.write_ep_surface(flr_name, type,construction, zone, 
                                                boundary,boundary_object, sun_exp, wind_exp, points)
                                                    
        #window 
        if self.windows !=[]:
            for window in self.windows:
                flr_data = flr_data + window.idf()
        #shade 
        if self.shades != []:
            for shade in self.shades:
                flr_data = flr_data + shade.idf()
                
        return flr_data
    
    #TODO MAKE THIS FUNCTION MORE GENERAL AS NOW IT ONLY DEALS WITH ONE WINDOW PER SURFACE
    def rad(self, interior = True):
        flr_name = self.name 
        flr_material = self.rad_material
        
        #wall with no windows
        if self.windows == []:
            flr_points = self.points[:]
            if interior == True:
                flr_points.reverse()
                
            rad_flr_data = radiance.write_surface(flr_name, flr_material, flr_points)
                
        #wall with windows
        if self.windows !=[]:
            window = self.windows[0]
            flr_points = self.points[:]
            win_points = window.points[:]
            if interior == True:
                flr_points.reverse()
                window_points = window.rad(interior = True )
            if interior == False:
                win_points.reverse()
                window_points = window.rad(interior = False )
                
            flr_win_points = flr_points  
            flr_win_points.append(flr_points[0])
            for win_point in win_points:
                flr_win_points.append(win_point)
            flr_win_points.append(win_points[0])
            rad_flr_data = radiance.write_surface(flr_name, flr_material, flr_win_points) + window_points
        
        #shade 
        if self.shades !=[]:
            for shade in self.shades:
                rad_flr_data = rad_flr_data + shade.rad()
                
        return rad_flr_data
    
   
class SubSurface(Surface):
    def __init__(self, name, points):
        super(SubSurface,self).__init__(name,  points)
        self.host_srf = None
        self.transmittance = "" 

class Window(SubSurface):
    
    def idf(self):
        if self.host_srf == None:
            raise Exception
        #DISTANCE BETWEEN 2 VECTORS 
        if len(self.points) > 3:
            distance1 = math.sqrt(( (math.pow((self.points[0][0]-self.points[2][0]),2)) +
            (math.pow((self.points[0][1]-self.points[2][1]),2)) +
            (math.pow((self.points[0][2]-self.points[2][2]),2)) ))
            
            distance2 = math.sqrt(( (math.pow((self.points[1][0]-self.points[3][0]),2)) +
            (math.pow((self.points[1][1]-self.points[3][1]),2)) +
            (math.pow((self.points[1][2]-self.points[3][2]),2)) ))
            
            if distance1 == distance2 :
                #ADD ONE MORE ANGLE CONDITION
                win_name = self.name 
                win_construction = self.idf_material
                build_surface = self.host_srf
                win_shading = ""
                win_frame = ""
                win_points = self.points
                window_data = energyplus.write_ep_window(win_name, win_construction, build_surface, win_shading, win_frame, win_points)   
                return window_data
            else :
                win_name = self.name + "-a"
                win_construction = self.idf_material
                build_surface = self.host_srf
                win_shading = ""
                win_frame = ""
                win_points = [self.points[0], self.points[1], self.points[2]]
                window_data = energyplus.write_ep_window(win_name, win_construction, build_surface, win_shading, win_frame, win_points)
                
                win_name = self.name + "-b"
                win_construction = self.idf_material
                build_surface = self.host_srf
                win_shading = ""
                win_frame = ""
                win_points = [self.points[2], self.points[3], self.points[0]]
                window_data = window_data +\
                energyplus.write_ep_window(win_name, win_construction, build_surface, win_shading, win_frame, win_points)
                   
                return window_data
        
    def rad(self, interior = True):
        win_name = self.name 
        win_material = self.rad_material
        if interior == True:
            win_points = self.points[:]
            win_points.reverse()
        if interior == False:
            win_points = self.points
        rad_window_data = radiance.write_surface(win_name, win_material, win_points)
        return rad_window_data

class Shade(SubSurface):
    
    def idf(self):
        if self.host_srf == None:
            raise Exception
        shade_name = self.name 
        base_surface = self.host_srf
        transmittance = self.transmittance
        shade_points = self.points
        shade_data = energyplus.write_ep_zone_shade(shade_name, base_surface, transmittance, shade_points)
        return shade_data
        
    def rad(self):
        shade_name = self.name 
        shade_material = self.rad_material
        shade_points = self.points
        #shade_points.reverse()
        rad_shade_data = radiance.write_surface(shade_name, shade_material, shade_points)
        return rad_shade_data
        